﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Schema;
using System.Xml;

using Yawf.Core.Logs;

namespace Yawf.Core.Xml {
	public class XmlValidator {
		private List<String> ErrorMessages = null;
		private Dictionary<String,String> ImportedSchemas = null;

		/// <summary>
		/// Reads a list of external schemas, including all external schemas and there external 
		/// schemas and so on..
		/// 
		/// </summary>
		/// <param name="externals">List of external schemas to include</param>
		/// <param name="settings">XmlReaderSettings to add schemas into</param>
		private void ReadExternalSchemas(XmlSchemaObjectCollection externals,XmlReaderSettings settings) {
			XmlSchemaExternal external;
			XmlSchema schema;
			int i;

			// Loop through imports & includes 
			for (i = 0; externals != null && i < externals.Count; i++) {
				external = (XmlSchemaExternal)externals[i];
				if (external != null && external.SchemaLocation != null && external.SchemaLocation != "") {
					if (!ImportedSchemas.ContainsKey(external.SchemaLocation)) {
						ImportedSchemas.Add(external.SchemaLocation,external.SchemaLocation);
						schema = XmlSchema.Read(XmlReader.Create(external.SchemaLocation,settings),null);
						ReadExternalSchemas(schema.Includes,settings);
						settings.Schemas.Add(schema);
					} 
				}
			}
		}						// ReadExternalSchemas

		/// <summary>
		/// Este metodo valida un archivo XML contra un esquema
		/// </summary>
		/// <param name="ruta">Ruta a archivo XML</param>
		/// <param name="rutaXsd">Ruta a archvio XSD</param>
		/// <returns>True si el XML es valido contra esquema, false si no</returns>
		public List<String> ValidarXmlDocument(String ruta,String rutaXsd) {
			return ValidarXmlDocument(new FileStream(ruta, FileMode.Open, FileAccess.Read), new FileStream(rutaXsd, FileMode.Open, FileAccess.Read));
		}						// ValidarXmlDocument

		/// <summary>
		/// Este metodo valida un archivo XML contra un esquema
		/// </summary>
		/// <param name="xmlStream">Stream a archivo XML</param>
		/// <param name="xsdStream">Stream a archvio XSD</param>
		/// <returns>True si el XML es valido contra esquema, false si no</returns>
		public List<String> ValidarXmlDocument(Stream xmlStream, Stream xsdStream) {
			XmlReaderSettings settings;
			XmlReader xmlReader = null;
			XmlSchema schema;
			XmlDocument xmlDoc;

			ErrorMessages = new List<String>();
			ImportedSchemas = new Dictionary<String,String>();
			try {
				// Leer esquema
				schema = XmlSchema.Read(xsdStream,null);

				// Agregar esquema a xmlReader settings
				settings = new XmlReaderSettings();
				settings.ProhibitDtd = false;

				// Add external schemas, need to do this cause validation does not include imports
				ReadExternalSchemas(schema.Includes,settings);

				// Leer esquema con cual vamos hacer validacion
				settings.Schemas.Add(schema);
				settings.ValidationType = ValidationType.Schema;
				settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
				settings.ValidationEventHandler += new ValidationEventHandler(this.ValidationHandler);

				// Leer XML, si se encuentra algun error debes tener valores en 
				// variable ErrorMessages
				xmlReader = XmlReader.Create(xmlStream,settings);
				xmlDoc = new XmlDocument();
				xmlDoc.Load(xmlReader);
			} catch (Exception e) {
				// Validation fallo, logear y tirar exception mas arriba
				Log.ExceptionMessage(this.GetType(), "Error de validation de esquema", e);
				ErrorMessages.Add("Error de validation de esquema, Exception " + e);
			}finally{
				if (xmlReader != null) {
					xmlReader.Close();
				}
			}

			return ErrorMessages;
		}						// ValidarXmlDocument

		/// <summary>
		/// Handler de errores de validation de esquemas.
		/// Este metodo graba cada error en la variable interna ErrorMessages
		/// </summary>
		/// <param name="sender">Objeto que origina el evento</param>
		/// <param name="args">Argumentos de validation</param>
		private void ValidationHandler(object sender, ValidationEventArgs args) {
			ErrorMessages.Add(args.Message);
		}

	}
}
